require 'rubygems'
require 'sinatra'
require 'datamapper'
require 'dm-core'
require 'dm-validations'
require 'logger'
require 'erb'
require 'json'

module Jsonable
	def to_json(*a)
		{
			"#{self.class.name}"   => get_instance_variables
		}.to_json(*a)
	end

	private

	def get_instance_variables
		h = Hash.new

		self.instance_variables.each do |k|
			h[k[1..-1]] = instance_variable_get k
		end

		h
	end
end

module Workflow

	class EmailStep
		include Jsonable

		attr_accessor :to, :from, :subject, :body

		def initialize
		end
	end

	class ApproveStep
		include Jsonable

		attr_accessor :approver

		def initialize
		end
	end

	class IfElseStep
		include Jsonable

		attr_accessor :condition, :if, :else

		def initialize
		end
	end

	class BeginStep
		include Jsonable

		attr_accessor :name

		def initialize
		end
	end

	class EndStep
		include Jsonable

		attr_accessor :ends_step

		def initialize
		end
	end

	class GotoStep
		include Jsonable

		attr_accessor :goto

		def initialize
		end
	end

	class Workflow
		include DataMapper::Resource
		property :id, Serial
		property :name, String
		property :description, String
		property :created_at, DateTime
		property :updated_at, DateTime
		property :rule_class_id, Integer
		property :enabled, Boolean, :default => false


		validates_presence_of :name

		has n, :steps
	end

	class Step
		include DataMapper::Resource
		property :id, Serial
		property :step_config, Text
		property :created_at, DateTime
		property :updated_at, DateTime
		property :step_position, Integer

		# pass in  a concrete step and save it as json
		def step=(val)
			self.step_config = val.to_json
		end

		# deserialize the json to a concrete step
		def step
			# parse json. returns ruby hash
			h = JSON.parse(self.step_config)
			# the first key is the name of the class
			class_name = h.keys.first



			o = Object.const_get(:Workflow).const_get(class_name.split( '::' )[1].to_sym).new
			# get the attributes
			a = h[class_name]
			a.each_pair do |k, v|
				o.instance_variable_set("@#{k}", v)
			end

			# return the initialized object
			o
		end

		def to_json(options)
			super( :exclude => :step_config, :methods => [:step] )
		end

		belongs_to :workflow
	end

	class Application < Sinatra::Base

		configure :development do
			DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/development.db")
			DataMapper::Logger.new(STDOUT, :debug)

			set :views, File.join(File.dirname(__FILE__), 'views')
			set :public, File.join(File.dirname(__FILE__), 'public')
			set :run, false
			set :env, (ENV['RACK_ENV'] ? ENV['RACK_ENV'].to_sym : :development)
		end

    configure :production do
      DataMapper.setup(:default, ENV['DATABASE_URL'] || 'sqlite3://my.db')
      set :views, File.join(File.dirname(__FILE__), 'views')
      set :public, File.join(File.dirname(__FILE__), 'public')
      set :run, false
      set :env, (ENV['RACK_ENV'] ? ENV['RACK_ENV'].to_sym : :production)
    end

		logger = Logger.new(STDOUT)

		### HELPERS

		def get_json_data
			JSON.parse(request.env['rack.input'].read)
		end


		### CONTROLLER

		before do
			cache_control :public, :must_revalidate, :max_age => 0
		end


		# load the main page
		get '/' do
			erb :workflow_overview
		end

		### AJAX endpoints which return J

		# get all workflows incuding steps
		# rule class id is optional
		get '/workflows/?:rule_class_id?' do
			status 200

			if params[:rule_class_id].nil? or params[:rule_class_id] == '0'
				all = Workflow.all(:order => [ :rule_class_id.asc ])
			else
				all = Workflow.all(:rule_class_id => params[:rule_class_id], :order => [ :rule_class_id.desc ])
			end

			all.to_json( :methods => [:steps] )
		end

		# get a specific workflow
		get '/workflow/:id' do
			status 200
			Workflow.get(params[:id]).to_json( :methods => [:steps] )
		end

		# create a new workflow
		post '/workflow/create' do
			status 201 # "Created"
			data = get_json_data
			w = Workflow.new
			w.created_at = Time.now
			w.updated_at = Time.now
			w.name = data['name']
			w.description = data['description']
			w.save
			w.to_json
		end

		# delete a specific workflow
		delete '/workflow/:id' do
			status 200
			w = Workflow.get(params[:id])
			w.destroy!
			body '' # no need to return content
		end

		# enable a specific workflow
		post '/workflow/:id/enable' do
			status 200
			w = Workflow.get params[:id]
			currentRuleClassId = w.rule_class_id

			# disable all currently enabled workflows
			all = Workflow.all(:rule_class_id => currentRuleClassId)
			all.each do |item|
				item.enabled = false
				item.save
			end

			# enable selected workflow
			w.enabled = true
			w.save
			w.to_json(:methods => [:steps])
		end

		# re order steps within a workflow
		put '/workflow/:id/redorder' do
			status 200
		end

		# add a step to a workflow
		post '/workflow/:id/step/create' do
			status 201 # "Created"
			data = get_json_data

			w = Workflow.get(params[:id])

			step = Step.new
			step.created_at = Time.now
			step.updated_at = Time.now
			step.step_config =  data.to_json

			w.steps << step
			w.save
			w.to_json( :methods => [ :steps ])
		end

		# remove a step from a workflow
		delete '/workflow/:id/steps/:step_id' do
			status 200

			body ''
		end

		# update a specific step
		put '/step/:id' do
			status 200
		end



		# create 10 workflows for testing. include all a approve and email step
		post '/test/init/data' do
			done = []
			rule_class_ids = [1,2,3,4]

			30.times.each do |n|
				w = Workflow.new
				w.name = "Workflow #{n}"
				w.description = "Some Description #{n}"
				w.created_at = Time.now
				w.updated_at = Time.now
				w.rule_class_id = rule_class_ids.shuffle.first

				s = Step.new(:created_at => Time.now, :updated_at => Time.now, :step_position => 1)
				s2 = Step.new(:created_at => Time.now, :updated_at => Time.now, :step_position => 2)

				a = ApproveStep.new
				a.approver = "stefan.siebel#{n}@concur.com"
				s.step = a
				e = EmailStep.new
				e.to = "stefan.siebel@concur.com"
				e.from = "newworkflow@concur.com"
				e.body = "Some text"
				e.subject = "you've been tested"
				s2.step = e

				w.steps << s
				w.steps << s2

				w.save
				done << w
			end

			# enable one workflow in each rule class
			rule_class_ids.each do |n|
				wfs = Workflow.all(:rule_class_id => n)
				wf = wfs.first
				wf.enabled = true
				wf.save
			end

			done.to_json
		end
	end
end

# auto migrate database based on model definition above.
DataMapper.auto_upgrade!